#include "MatrixMultiplication.h"


const int matrixHeight1 = 1035;
const int matrixWidth1 = 1013;
const int matrixHeight2 = 1013;
const int matrixWidth2 = 1087;

const floatPair matrixRange = floatPair(0.0f, 0.5f);

int main() {
	freopen("output.txt", "w", stdout);
	initializeMatrix(matrixHeight1, matrixWidth1, matrixHeight2, matrixWidth2, matrixRange);
	multiplyUsingKernels();
	return 0;
}

void initializeMatrix(int matrixHeight1, int matrixWidth1, int matrixHeight2, int matrixWidth2, floatPair matrixRange) {
	cout << "Initializing Matrices." << endl;
	cout << endl;

	cout << "Matrix 1: " << endl;
	cout << "Height: " << matrixHeight1 << ", Width: " << matrixWidth1 << "." << endl; 
	float *matrix1 = createMatrix(matrixHeight1, matrixWidth1, matrixRange.first, matrixRange.second);
	writeMatrixToFile(matrix1, matrixHeight1, matrixWidth1, "matrix1.txt");
	cout << "Matrix 1 written to file \"matrix1.txt\"." << endl;
	cout << endl;

	cout << "Matrix 2: " << endl;
	cout << "Height: " << matrixHeight2 << ", Width: " << matrixWidth2 << "." << endl; 
	float *matrix2 = createMatrix(matrixHeight2, matrixWidth2, matrixRange.first, matrixRange.second);
	writeMatrixToFile(matrix2, matrixHeight2, matrixWidth2, "matrix2.txt");
	cout << "Matrix 2 written to file \"matrix2.txt\"." << endl;
	cout << endl;

	float *result = multiplySequencially(matrix1, matrixHeight1, matrixWidth1, matrix2, matrixHeight2, matrixWidth2);
	writeMatrixToFile(result, matrixHeight1, matrixWidth2, "result.txt");
	cout << "Multiplication result written to \"result.txt\"." << endl;
	cout << endl;
}

float* multiplySequencially(float *matrix1, int height1, int width1, float *matrix2, int height2, int width2) {
	cout << "Multiplying the 2 matrices sequencially." << endl;
	if (width1 != height2) {
		cout << "ERROR: Incompatible sizes." << endl;
		exit(0);
	}
	
	clock_t time = clock();
	
	int height = height1;
	int width = width2;
	int length = width1;

	float *result = new float[height * width];
	for (int row = 0; row < height; ++row)
		for (int col = 0; col < width; ++col) {
			float cell = 0.0f;
			for (int k = 0; k < length; ++k)
				cell += matrix1[row * width1 + k] * matrix2[k * width2 + col];
			result[row * width + col] = cell;
		}

	time = clock() - time;
	cout << "Multiplication finished, Evaluation time: " << time << " milliseconds." << endl;
	cout << "GFLOPS = " << (1e-6 * 2.0 * height * width * length) / time << "." << endl;
	return result;
}

float *createMatrix(int height, int width, float lowerBound, float upperBound) {

	srand(time(NULL));
	float range = upperBound - lowerBound;
	float factor = range / RAND_MAX;
	float offset = lowerBound;

	float *matrix = new float[height * width];

	for (int row = 0; row < height; ++row)
		for (int col = 0; col < width; ++col) {
			float cell = (float)rand() * factor + offset;
			matrix[row * width + col] = cell;
		}

	return matrix;
}

float *createZeroMatrix(int height, int width) {

	float *matrix = new float[height * width];

	for (int row = 0; row < height; ++row)
		for (int col = 0; col < width; ++col)
			matrix[row * width + col] = 0;

	return matrix;
}

void writeMatrixToFile(float *matrix, int height, int width, string fileName) {
	
	ofstream file;
	file.open(fileName.c_str());
	file << height << " " << width << endl;
	for (int row = 0; row < height; ++row) {
		for (int col = 0; col < width; ++col) {
			file << matrix[row * width + col] << " ";
		}
		file << endl;
	}

	file.close();
}

void readMatrixFromFile(float *&matrix, int &height, int &width, string fileName) {
	
	ifstream file;
	file.open(fileName.c_str());

	file >> height >> width;

	matrix = new float[height * width];
	
	for (int row = 0; row < height; ++row)
		for (int col = 0; col < width; ++col)
			file >> matrix[row * width + col];

	file.close();
}

bool verify(const float *result, const float *correctResult, const int height, const int width) {

	for (int row = 0; row < height; ++row)
		for (int col = 0; col < width; ++col)
			if (fabs(result[row * width + col] - correctResult[row * width + col]) > 1e-3)
				return 0;

	return 1;
}